package org.wangduqing.ibatisdao.aop;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import ognl.Ognl;
import ognl.OgnlException;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.simple.ParameterizedBeanPropertyRowMapper;
import org.wangduqing.ibatisdao.anotation.Arguments;
import org.wangduqing.ibatisdao.anotation.ResultType;
import org.wangduqing.ibatisdao.anotation.Sql;
import org.wangduqing.ibatisdao.def.IbatisDaoConstants;
import org.wangduqing.ibatisdao.hibernate.dao.IGenericBaseCommonDao;
import org.wangduqing.ibatisdao.pojo.IbatisDaoPage;
import org.wangduqing.ibatisdao.spring.rowMapper.GenericRowMapper;
import org.wangduqing.ibatisdao.spring.rowMapper.IbatisColumnOriginalMapRowMapper;
import org.wangduqing.ibatisdao.util.FreemarkerParseFactory;

import org.wangduqing.ibatisdao.util.IbatisDaoUtil;

public class IbatisDaoHandler implements MethodInterceptor {
	
	private static final Logger logger = Logger.getLogger(IbatisDaoHandler.class);
	//此模板需要通过spring容器注入
	private JdbcTemplate jdbcTemplate;
	private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
	private IGenericBaseCommonDao ibatisDaoHiberCommonDao;
	
	private BasicFormatterImpl formatter = new BasicFormatterImpl();
	
	private String UPPER_KEY = "upper";
	private String LOWER_KEY = "lower";
	
	private String keyType = "origin";
	private boolean formatSql = false;
	private boolean showSql = false;
	private String dbType;
	

	@Override
	public Object invoke(MethodInvocation methodInvocation) throws Throwable {
		
		Method method = methodInvocation.getMethod();
		Object[] args = methodInvocation.getArguments();
		Object returnObj = null;
		String templateSql = null;
		Map<String, Object> sqlParamsMap = new HashMap<String, Object>();
		IbatisDaoPage pageSetting = new IbatisDaoPage();
		if(!IbatisDaoUtil.isAbstract(method)){
			
			return methodInvocation.proceed();
			
		}
		Map<String, Object> rs = new HashMap();
		if(ibatisDaoHiber(rs, method, args)){
			
			return rs.get("returnObj");
			
		}
		templateSql = installDaoMetaData(pageSetting, method, sqlParamsMap, args);
		String executeSql = parseSqlTemplate(method, templateSql, sqlParamsMap);
		Map<String, Object> sqlMap = installPlaceholderSqlParam(executeSql, sqlParamsMap);
		returnObj = getReturnIbatisDaoResult(dbType, pageSetting, jdbcTemplate, method, executeSql, sqlMap);
		if(showSql){
			
			logger.info("MiniDao-SQL:\n\n"+(formatSql == true?formatter.format(executeSql):executeSql)+"\n");
			
		}
		
		return null;
	}
	
	private boolean ibatisDaoHiber(Map rs, Method method, Object[] args){
		
		if(IbatisDaoConstants.METHOD_SAVE_BY_HIBER.equals(method.getName())){
			
			ibatisDaoHiberCommonDao.save(args[0]);
			return true;
			
		}
		if(IbatisDaoConstants.METHOD_GET_BY_ID_HIBER.equals(method.getName())){
			
			Class<?> clz = (Class<?>)args[0];
			rs.put("returnObj", ibatisDaoHiberCommonDao.get(clz, args[1].toString()));
			return true;
			
		}
		if(IbatisDaoConstants.METHOD_GET_BY_ENTITY_HIBER.equals(method.getName())){
			
			rs.put("returnObj", ibatisDaoHiberCommonDao.get(args[0]));
			return true;
			
		}
		if(IbatisDaoConstants.METHOD_UPDATE_BY_HIBER.equals(method.getName())){
			
			ibatisDaoHiberCommonDao.saveOrUpdata(args[0]);
			return true;
			
		}
		if(IbatisDaoConstants.METHOD_DELETE_BY_HIBER.equals(method.getName())){
			
			ibatisDaoHiberCommonDao.delete(args[0]);
			return true;
			
		}
		if(IbatisDaoConstants.METHOD_DELETE_BY_ID_HIBER.equals(method.getName())){
			
			Class<?> clz = (Class<?>) args[0];
			ibatisDaoHiberCommonDao.deleteEntityById(clz, args[1].toString());
			return true;
			
		}
		if(IbatisDaoConstants.METHOD_LIST_BY_HIBER.equals(method.getName())){
			
			rs.put("returnObj", ibatisDaoHiberCommonDao.loadAll(args[0]));
			return true;
			
		}
		return false;
		
	}
	
	private String installDaoMetaData(IbatisDaoPage pageSetting, Method method, Map<String, Object> sqlParamsMap, Object[] args) throws Exception{
		
		String templateSql = null;
		boolean arguments_flag = method.isAnnotationPresent(Arguments.class);
		if(arguments_flag){
			
			Arguments arguments = method.getAnnotation(Arguments.class);
			logger.debug("@Arguments------------------------------------------"+Arrays.toString(arguments.value()));
			if(arguments.value().length>args.length){
				
				throw new Exception("[注释标签]参数数目，不能大于[方法参数]参数数目");
				
			}
			int args_num = 0;
			for(String v:arguments.value()){
				
				if(v.equalsIgnoreCase("page")){
					
					pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
					
				}
				if(v.equalsIgnoreCase("rows")){
					
					pageSetting.setPage(Integer.parseInt(args[args_num].toString()));
					
				}
				sqlParamsMap.put(v, args[args_num]);
				args_num++;
				
			}
			
		}else{
			
			if(args.length>1){
				
				throw new Exception("方法参数数目>=2，方法必须使用注释标签@Arguments");
				
			}else if(args.length==1){
				
				sqlParamsMap.put(IbatisDaoConstants.SQL_FTL_DTO, args[0]);
				
			}
			
		}
		if(method.isAnnotationPresent(Sql.class)){
			
			Sql sql = method.getAnnotation(Sql.class);
			if(StringUtils.isNotEmpty(sql.value())){
				
				templateSql = sql.value();
				
			}
			logger.debug("@Sql------------------------------------------"+sql.value());
			
		}
		return templateSql;
		
	}
	
	private String getSqlText(String sql){
		
		return sql.replace("\\n", " ").replace("\\t", " ").replace("\\s{1,}", " ").trim();
		
	}
	
	private String parseSqlTemplate(Method method, String templateSql, Map<String, Object> sqlParamsMap){
		
		String executeSql = null;
		if(StringUtils.isNotEmpty(templateSql)){
			
			executeSql = new FreemarkerParseFactory().parseTemplateContent(templateSql, sqlParamsMap);
			                 
		}else{
			
			String sqlTemplatePath = "/" + method.getDeclaringClass().getName().replace(".", "/").replace("/dao/", "/sql/") + "_" + method.getName() + ".sql";
			URL sqlFileUrl = this.getClass().getClassLoader().getResource(sqlTemplatePath);
			if(sqlFileUrl==null){
				
				sqlTemplatePath = "/" + method.getDeclaringClass().getName().replace(".", "/") + "_" +method.getName() + ".sql";
				
			}
			logger.debug("MiniDao-SQL-Path:"+sqlTemplatePath);
			executeSql = new FreemarkerParseFactory().parseTemplate(sqlTemplatePath, sqlParamsMap);
			
		}
		return getSqlText(executeSql);
		
	}
	
	private Map<String, Object> installPlaceholderSqlParam(String executeSql,Map sqlParamsMap) throws OgnlException{
		
		Map<String, Object> map = new HashMap<String, Object>();
		String regEx = ":[ tnx0Bfr]*[0-9a-z.A-Z]+";
		Pattern pat = Pattern.compile(regEx);
		Matcher m = pat.matcher(executeSql);
		while(m.find()){
			
			logger.debug(" Match ["+m.group()+"] at positions "+m.start()+"-"+(m.end()-1));
			String ognl_key = m.group().replace(":", "").trim();
			map.put(ognl_key, Ognl.getValue(ognl_key, sqlParamsMap));
			
		}
		return map;
		
	}
	
	private static boolean checkActiveKey(String methodName){
		
		String keys[] = IbatisDaoConstants.INF_METHOD_ACTIVE.split(",");
		for(String s:keys){
			
			if(methodName.startsWith(s)){
				
				return true;
				
			}
			
		}
		return false;
		
	}
	
	private static boolean checkBatchKey(String methodName){
		
		String keys[] = IbatisDaoConstants.INF_METHOD_BATCH.split(",");
		for(String s:keys){
			
			if(methodName.startsWith(s)){
				
				return true;
				
			}
			
		}
		return false;
		
	}
	
	private void addResultArray(int[] result, int index, int[] arr){
		
		int length = arr.length;
		for(int i=0; i<length; i++){
			
			result[index-length+i] = arr[i];
			
		}
		
	}
	
	private int[] batchUpdate(JdbcTemplate jdbcTemplate, String executeSql){
		
		String[] sqls = executeSql.split(";");
		if(sqls.length<100){
			
			return jdbcTemplate.batchUpdate(sqls);
			
		}
		
		int[] result = new int[sqls.length];
		List<String> sqlList = new ArrayList<String>();
		for(int i=0;i<sqls.length;i++){
			
			sqlList.add(sqls[i]);
			if(i%100==0){
				
				addResultArray(result, i+1, jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
				sqlList.clear();
				
			}
			
		}
		addResultArray(result, sqls.length, jdbcTemplate.batchUpdate(sqlList.toArray(new String[0])));
		return result;
		
	}
	
	private RowMapper<Map<String, Object>> getColumnMapRowMapper(){
		
		if(getKeyType().equalsIgnoreCase(LOWER_KEY)){
			
			return new IbatisColumnOriginalMapRowMapper();
			
		}else if(getKeyType().equalsIgnoreCase(UPPER_KEY)){
			
			return new ColumnMapRowMapper();
			
		}else{
			
			return new IbatisColumnOriginalMapRowMapper();
			
		}
		
		
	}
	
	private Object getReturnIbatisDaoResult(String dbType, IbatisDaoPage pageSetting, JdbcTemplate jdbcTemplate, Method method, String executeSql, Map<String, Object> paramMap){
		
		String methodName = method.getName();
		if(checkActiveKey(methodName)){
			
			if(paramMap!=null){
				
				return namedParameterJdbcTemplate.update(executeSql, paramMap);
				
			}else{
				
				return jdbcTemplate.update(executeSql);
				
			}
			
		}else if(checkBatchKey(methodName)){
			
			return batchUpdate(jdbcTemplate, executeSql);
			
		}else{
			
			Class<?> returnType = method.getReturnType();
			if(returnType.isPrimitive()){
				
				Number number = jdbcTemplate.queryForObject(executeSql, BigDecimal.class);
				if("int".equals(returnType)){
					
					return number.intValue();
					
				}else if("long".equals(returnType)){
					
					return number.longValue();
					
				}else if("double".equals(returnType)){
					
					return number.doubleValue();
					
				}
				
			}else if(returnType.isAssignableFrom(List.class)){
				
				int page = pageSetting.getPage();
				int rows = pageSetting.getRows();
				if(page!=0&&rows!=0){
					
					executeSql = IbatisDaoUtil.createPageSql(dbType, executeSql, page, rows);
					
				}
				ResultType resultType = method.getAnnotation(ResultType.class);
				String[] values = null;
				if(resultType!=null){
					
					values = resultType.value();
					
				}
				if(values==null||values.length==0||"java.util.Map".equals(values[0])){
					
					if(paramMap!=null){
						
						return namedParameterJdbcTemplate.query(executeSql, paramMap, getColumnMapRowMapper());
						
					}else{
						
						return jdbcTemplate.query(executeSql, getColumnMapRowMapper());
						
					}
					
				}else{
					
					Class clazz = null;
					try {
						
						clazz = Class.forName(values[0]);
						
					} catch (ClassNotFoundException e) {
						
						e.printStackTrace();
						
					}
					if(paramMap!=null){
						
						return namedParameterJdbcTemplate.query(executeSql, paramMap, new GenericRowMapper(clazz));
						
					}else{
						
						return jdbcTemplate.query(executeSql, new GenericRowMapper(clazz));
						
					}
					
				}
				
			}else if(returnType.isAssignableFrom(Map.class)){
				
				if(paramMap!=null){
					
					return (Map)namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, getColumnMapRowMapper());
					
				}else{
					
					return (Map)jdbcTemplate.queryForObject(executeSql, getColumnMapRowMapper());
					
				}
				
			}else if(returnType.isAssignableFrom(String.class)){
				
				try {
					
					if(paramMap!=null){
						
						return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, String.class);
						
					}else{
						
						return jdbcTemplate.queryForObject(executeSql, String.class);
						
					}
					
				} catch (EmptyResultDataAccessException e) {
					
					return null;
					
				}
				
			}else if(IbatisDaoUtil.isWrapClass(returnType)){
				
				try {
					
					if(paramMap!=null){
						
						return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, returnType);
						
					}else{
						
						return jdbcTemplate.queryForObject(executeSql, returnType);
						
					}
					
				} catch (EmptyResultDataAccessException e) {
					
					return null;
					
				}
				
			}else{
				
				RowMapper<?> rm = ParameterizedBeanPropertyRowMapper.newInstance(returnType);
				try {
					
					if(paramMap!=null){
						
						return namedParameterJdbcTemplate.queryForObject(executeSql, paramMap, rm);
						
					}else{
						
						return jdbcTemplate.queryForObject(executeSql, rm);
						
					}
					
				} catch (EmptyResultDataAccessException e) {
					
					return null;
					
				}
				
			}
			
		}
		return null;
		
	}


	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}


	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}


	public IGenericBaseCommonDao getIbatisDaoHiberCommonDao() {
		return ibatisDaoHiberCommonDao;
	}


	public void setIbatisDaoHiberCommonDao(
			IGenericBaseCommonDao ibatisDaoHiberCommonDao) {
		this.ibatisDaoHiberCommonDao = ibatisDaoHiberCommonDao;
	}


	public String getKeyType() {
		return keyType;
	}


	public void setKeyType(String keyType) {
		this.keyType = keyType;
	}


	public String getDbType() {
		return dbType;
	}


	public void setDbType(String dbType) {
		this.dbType = dbType;
	}

	public boolean isFormatSql() {
		return formatSql;
	}

	public void setFormatSql(boolean formatSql) {
		this.formatSql = formatSql;
	}

	public boolean isShowSql() {
		return showSql;
	}

	public void setShowSql(boolean showSql) {
		this.showSql = showSql;
	}

	public NamedParameterJdbcTemplate getNamedParameterJdbcTemplate() {
		return namedParameterJdbcTemplate;
	}

	public void setNamedParameterJdbcTemplate(
			NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
		this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
	}
	
}
